﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Xml;
using Cubicle.Core.Shared;

namespace Cubicle.Core.Hosting
{
    public class EngineContractResolver : DataContractResolver
    {
        // Type.Namespace => Type.Name => Type
        private readonly Dictionary<string, Dictionary<string, Type>> _nameToType =
            new Dictionary<string, Dictionary<string, Type>>();

        // Universal namespace definition
        private const string NullNamepace = "nsExo";

        private bool _allowManualSearch = false;

        #region Overrides of DataContractResolver

        public override bool TryResolveType(Type type, Type declaredType, DataContractResolver knownTypeResolver, out XmlDictionaryString typeName, out XmlDictionaryString typeNamespace)
        {
            // First check the known type resolver
            var isKnown = knownTypeResolver.TryResolveType(type, declaredType, null, out typeName, out typeNamespace);
            if (isKnown) return true;

            // Now check if this is a special case
            //var attrib = (DataContractAttribute)type.GetCustomAttributes(typeof(DataContractAttribute), true).FirstOrDefault();
            //if (attrib == null) return false;
            
            var dictionary = new XmlDictionary();
            var name = type.FullName ?? type.Name;
            var ns = type.Namespace ?? NullNamepace;
            typeName = dictionary.Add(name);
            typeNamespace = dictionary.Add(ns);

            UpdateCache(ns, name, type);
            return true;
        }

        public override Type ResolveName(string typeName, string typeNamespace, Type declaredType, DataContractResolver knownTypeResolver)
        {
            // Check if this is some kind of funky generic type
            var found = Type.GetType(typeName);
            if (found == null) 
                found = Type.GetType(typeNamespace + "." + typeName);

            if (found == null)
            {
                // Check if this has been cached
                if (_nameToType.ContainsKey(typeNamespace) && _nameToType[typeNamespace].ContainsKey(typeName))
                    return _nameToType[typeNamespace][typeName];

                // Resolve it directly from the Cache (no .Net assemblies)
                found = TypeCache.GetType(typeNamespace, typeName);

                // Try to resolve it (including .Net assemblies)
                if (found == null)
                {
                    var fullName = typeNamespace + "." + typeName;
                    found = Type.GetType(fullName);

                    // Now try to find it manually (dynamically loaded .Net assemblies?)
                    if (found == null && _allowManualSearch)
                        found = AppDomain.CurrentDomain
                            .GetAssemblies()
                            .SelectMany(a => a.GetTypes())
                            .Where(t => t.GetCustomAttributes(typeof(DataContractAttribute), true).FirstOrDefault() != null)
                            .Where(t => t.Name == typeName && t.Namespace == typeNamespace)
                            .FirstOrDefault();
                }
            }

            if (found != null)
            {
                UpdateCache(typeNamespace, typeName, found);
                return found;                
            }

            // Check known types
            return 
                knownTypeResolver.ResolveName(typeName, typeNamespace, declaredType, null);
        }

        #endregion

        private void UpdateCache(string typeNamespace, string typeName, Type type)
        {
            if (!_nameToType.ContainsKey(typeNamespace))
                _nameToType[typeNamespace] = new Dictionary<string, Type>();
            _nameToType[typeNamespace][typeName] = type;
        }

    }
}